package main

import (
	"fmt"
	"os"
	"strings"
	"text/template"
)

/*
使用Parse代替ParseFiles，因为Parse可以直接测试一个字符串，而不需要额外的文件
不使用handler来写演示代码，而是每个测试一个main，方便测试
使用os.Stdout代替http.ResponseWriter，因为os.Stdout实现了io.Writer接口

Go语言的模板通过{{}}来包含需要在渲染时被替换的字段，{{.}}表示当前的对象，这和Java或者C++中的this类似，
如果要访问当前对象的字段通过{{.FieldName}}，但是需要注意一点：这个字段必须是导出的(字段首字母必须是大写的)(公共的），
否则在渲染的时候就会报错
调用了一个未导出的字段，但是如果我们调用了一个不存在的字段是不会报错的，而是输出为空。

上面我们例子展示了如何针对一个对象的字段输出，那么如果字段里面还有对象，如何来循环的输出这些内容呢？我们可以使用{{with …}}…{{end}}和{{range …}}{{end}}来进行数据的输出。
{{range}} 这个和Go语法里面的range类似，循环操作数据
{{with}}操作是指当前对象的值，类似上下文的概念
*/

type Person struct {
	UserName string
	Emails   []string
	Friends  []*Friend
}

type Friend struct {
	FName string
}

func test() {
	t := template.New("fieldname example")
	t, _ = t.Parse("hello {{.UserName}}!")
	p := Person{UserName: "dralee"}
	t.Execute(os.Stdout, p)
	fmt.Println()
}

func testEscape() {
	fmt.Println("with xxx end & range xxx end")
	t := template.New("fieldname example")
	f1 := Friend{FName: "katti"}
	f2 := Friend{FName: "jinwhat"}
	t, _ = t.Parse(`hello {{.UserName}}!
	{{range .Emails}}
		an email {{.}}
	{{end}}
	{{with .Friends}}
	 {{range .}}
		my friend's name is {{.FName}}
	 {{end}}
	{{end}}
	`)
	p := Person{UserName: "dralee",
		Emails:  []string{"dralee@126.com", "dralee@gmail.com", "dralee@go.com"},
		Friends: []*Friend{&f1, &f2},
	}
	t.Execute(os.Stdout, p)
}

/*
注意：if里面无法使用条件判断，例如.Mail=="astaxie@gmail.com"，这样的判断是不正确的，if里面只能是bool值
*/
func testIf() {
	tEmpty := template.New("template demo")
	tEmpty = template.Must(tEmpty.Parse("空 pipeline if demo: {{if ``}} 不会输出.{{end}}\n"))
	tEmpty.Execute(os.Stdout, nil)

	tWithValue := template.New("template test")
	tWithValue = template.Must(tWithValue.Parse("不为空的pipeline if demo: {{if `anything`}} 我有内容，我会输出.{{end}}\n"))
	tWithValue.Execute(os.Stdout, nil)

	tIfElse := template.New("template test")
	tIfElse = template.Must(tIfElse.Parse("if-else demo: {{if `anything`}} if部分 {{else}} else部分.{{end}}"))
	tIfElse.Execute(os.Stdout, nil)
}

/*
Unix用户已经很熟悉什么是pipe了，ls | grep "beego"类似这样的语法你是不是经常使用，过滤当前目录下面的文件，
显示含有"beego"的数据，表达的意思就是前面的输出可以当做后面的输入，最后显示我们想要的数据，而Go语言模板最强
大的一点就是支持pipe数据，在Go语言里面任何{{}}里面的都是pipelines数据
如果还有一些可能引起XSS注入的可通过如下转化为html内容
{{. | html}}
*/

func EmailDealWith(args ...interface{}) string {
	ok := false
	var s string
	if len(args) == 1 {
		s, ok = args[0].(string)
	}
	if !ok {
		s = fmt.Sprint(args...) // ...
	}
	substrs := strings.Split(s, "@")
	if len(substrs) != 2 {
		return s
	}
	return substrs[0] + " at " + substrs[1] // 将@替换成at
}

/*
模板包内部已经有内置的实现函数
var builtins = FuncMap{
	"and":      and,
	"call":     call,
	"html":     HTMLEscaper,
	"index":    index,
	"js":       JSEscaper,
	"len":      length,
	"not":      not,
	"or":       or,
	"print":    fmt.Sprint,
	"printf":   fmt.Sprintf,
	"println":  fmt.Sprintln,
	"urlquery": URLQueryEscaper,
}
*/
func testTemplateFunc() {
	f1 := Friend{FName: "minux.anm"}
	f2 := Friend{FName: "xushiwei"}
	t := template.New("fieldname example")
	t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith}) // 模板函数
	t, _ = t.Parse(`hello {{.UserName}}!
	{{range .Emails}}
		an email {{.|emailDeal}}
	{{end}}
	{{with .Friends}}
	 {{range .}}
		my friend's name is {{.FName}}
	 {{end}}
	{{end}}
	`)
	p := Person{UserName: "dralee",
		Emails:  []string{"dralee@126.com", "dralee@gmail.com", "dralee@go.com"},
		Friends: []*Friend{&f1, &f2},
	}
	t.Execute(os.Stdout, p)
}

/*
模板包里面有一个函数Must，它的作用是检测模板是否正确，例如大括号是否匹配，注释是否正确的关闭，变量是否正确的书写
*/
func testMust() {
	tOk := template.New("first")
	template.Must(tOk.Parse(" some static text /* and a comment */"))
	fmt.Println("The first one parsed OK.")

	template.Must(template.New("second").Parse("some static text {{.Name}}"))
	fmt.Println("The second one parsed OK.")

	fmt.Println("The next one ought to fail.")
	tErr := template.New("check parse error with Must")
	template.Must(tErr.Parse(" some static text {{.Name}"))
}

/*
嵌套模板：
header->content->footer
每一个定义的{{define}}都是一个独立的模板，他们相互独立，是并行存在的关系，内部其实存储的是类似map的一种关系(key是模板的名称，value是模板的内容)，
然后我们通过ExecuteTemplate来执行相应的子模板内容，我们可以看到header、footer都是相对独立的，都能输出内容，content 中因为嵌套了header和
footer的内容，就会同时输出三个的内容。但是当我们执行s1.Execute，没有任何的输出，因为在默认的情况下没有默认的子模板，所以不会输出任何的东西。
* 同一个集合类的模板是互相知晓的，如果同一模板被多个集合使用，则它需要在多个集合中分别解析
*/
func testNesting() {
	s1, _ := template.ParseFiles("tpls/header.tmpl", "tpls/content.tmpl", "tpls/footer.tmpl")
	s1.ExecuteTemplate(os.Stdout, "header", nil)
	fmt.Println("=======================")
	s1.ExecuteTemplate(os.Stdout, "content", nil)
	fmt.Println("=======================")
	s1.ExecuteTemplate(os.Stdout, "footer", nil)
	fmt.Println("=======================")
	s1.Execute(os.Stdout, nil)
}

func main() {
	test()
	testEscape()
	testIf()
	testTemplateFunc()
	testNesting()
	testMust()
}
